using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;

namespace TimeWar
{
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    /// <summary>
    /// Basic 3D Object
    /// </summary>
    public class ThreeDimensionalObject
    {
        #region Variables

        /// <summary>
        /// The Object's Position in 3D Space
        /// </summary>
        protected Vector3 objPosition;

        protected Model object3D;

        /// <summary>
        /// The Object's Rotation in 3D Space
        /// </summary>
        protected Quaternion objRotation;

        /// <summary>
        /// The Objects Scale
        /// </summary>
        protected float objScaleX;
        protected float objScaleY;
        protected float objScaleZ;

        protected float aspectRatio;

        protected String modelLocation = null;

        protected Matrix view;

        #endregion

        #region Public Properties

        /// <summary>
        /// The Object's Position in 3D Space
        /// </summary>
        public Vector3 Position
        {
            get { return objPosition; }
            set { objPosition = value; }
        }

        public float PositionX
        {
            get { return objPosition.X; }
            set { objPosition.X = value; }
        }

        public float PositionY
        {
            get { return objPosition.Y; }
            set { objPosition.Y = value; }
        }

        public float PositionZ
        {
            get { return objPosition.Z; }
            set { objPosition.Z = value; }
        }

        protected Vector3 cameraPosition = new Vector3(0.0f, 50.0f, 5000.0f);

        public Vector3 CameraPosition
        {
            get { return cameraPosition; }
            set { cameraPosition = value; }
        }

        /// <summary>
        /// The Object's Rotation in 3D Space
        /// </summary>
        public Quaternion Rotation
        {
            get { return objRotation; }
            set { objRotation = value; }
        }

        public float RotationX
        {
            get { return objRotation.X; }
            set { objRotation.X = value; }
        }

        public float RotationY
        {
            get { return objRotation.Y; }
            set { objRotation.Y = value; }
        }

        public float RotationZ
        {
            get { return objRotation.Z; }
            set { objRotation.Z = value; }
        }

        public String ModelLocation
        {
            get { return modelLocation; }
            set { modelLocation = value; }
        }

        /// <summary>
        /// The Object's Scale
        /// </summary>
        public float Scale
        {
            set
            {
                objScaleX = value;
                objScaleY = value;
                objScaleZ = value;
            }
        }

        public float ScaleX
        {
            get { return objScaleX; }
            set { objScaleX = value; }
        }

        public float ScaleY
        {
            get { return objScaleY; }
            set { objScaleY = value; }
        }

        public float ScaleZ
        {
            get { return objScaleZ; }
            set { objScaleZ = value; }
        }

        public Matrix View
        {
            set { view = value; }
            get { return view; }
        }

        #endregion

        #region Constructor

        protected void LoadContent()
        {

            object3D = TimeWarGame.ContentManager.Load<Model>(modelLocation);
            //dalekModel = gameObj.Content.Load<Model>("Content\\Models\\Sovereign");

            //aspectRatio = (float)gameObj.GraphicsDevice.Viewport.Width / (float)gameObj.GraphicsDevice.Viewport.Height;
            aspectRatio = 16 / 9;
        }


        /// <summary>
        /// Create Object Instance
        /// </summary>
        public ThreeDimensionalObject()
        {
            objPosition = new Vector3(0, 0, 0);
            objRotation = new Quaternion(0, 0, 0, 1);
            objScaleX = 1.0f;
            objScaleY = 1.0f;
            objScaleZ = 1.0f;

        }

        public void Initialize()
        {
            LoadContent();
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Move Object Forwards (Object's Z Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void MoveForwards(float distance)
        { Translate(new Vector3(0.0f, 0.0f, -distance)); }

        /// <summary>
        /// Move Object Backwards (Object's Z Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void MoveBackwards(float distance)
        { Translate(new Vector3(0.0f, 0.0f, distance)); }

        /// <summary>
        /// Strafe Object Left (Object's X Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void StrafeLeft(float distance)
        { Translate(new Vector3(-distance, 0.0f, 0.0f)); }

        /// <summary>
        /// Strafe Object Right (Object's X Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void StrafeRight(float distance)
        { Translate(new Vector3(distance, 0.0f, 0.0f)); }

        /// <summary>
        /// Strafe Object Up (Object's Y Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void StrafeUp(float distance)
        { Translate(new Vector3(0.0f, distance, 0.0f)); }

        /// <summary>
        /// Strafe Object Down (Object's Y Axis)
        /// </summary>
        /// <param name="distance">Distance to Translate Object</param>
        public virtual void StrafeDown(float distance)
        { Translate(new Vector3(0.0f, -distance, 0.0f)); }

        /// <summary>
        /// Roll Object Left (Object's Z Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void RollLeft(float angleDegrees)
        { Rotate(Vector3.Forward, MathHelper.ToRadians(-angleDegrees)); }

        /// <summary>
        /// Roll Object Right (Object's Z Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void RollRight(float angleDegrees)
        { Rotate(Vector3.Forward, MathHelper.ToRadians(angleDegrees)); }

        /// <summary>
        /// Pitch Object Down (Object's X Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void PitchDown(float angleDegrees)
        { Rotate(Vector3.Left, MathHelper.ToRadians(angleDegrees)); }

        /// <summary>
        /// Pitch Object Up (Object's X Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void PitchUp(float angleDegrees)
        { Rotate(Vector3.Left, MathHelper.ToRadians(-angleDegrees)); }

        /// <summary>
        /// Rotate Object Left (Object's Y Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void RotateLeft(float angleDegrees)
        { Rotate(Vector3.Up, MathHelper.ToRadians(angleDegrees)); }

        /// <summary>
        /// Turn Object Right (Object's Y Axis)
        /// </summary>
        /// <param name="angleDegrees">Angle to Rotate Object</param>
        public virtual void RotateRight(float angleDegrees)
        { Rotate(Vector3.Up, MathHelper.ToRadians(-angleDegrees)); }

        /// <summary>
        /// Move Object towards Target Vector
        /// </summary>
        /// <param name="target">Target Vector</param>
        /// <param name="factor">Percentage of Translation</param>
        public virtual void MoveTo(Vector3 target, float factor)
        { Position = Vector3.Lerp(Position, target, factor); }

        /// <summary>
        /// Rotate Object towards Target Quaternion
        /// </summary>
        /// <param name="slerpTarget">Target Quaternion</param>
        /// <param name="factor">Percentage of Rotation</param>
        public virtual void Slerp(Quaternion slerpTarget, float factor)
        { objRotation = Quaternion.Slerp(objRotation, slerpTarget, factor); }

        /// <summary>
        /// Rotate Object towards Target Vector
        /// </summary>
        /// <param name="target">Target Vector</param>
        /// <param name="factor">Percentage of Rotation</param>
        public virtual void LookAt(Vector3 target, float factor)
        {
            Vector3 tminusp = target - objPosition;
            Vector3 ominusp = Vector3.Forward;

            tminusp.Normalize();

            float theta = (float)System.Math.Acos(Vector3.Dot(tminusp, ominusp));
            Vector3 cross = Vector3.Cross(ominusp, tminusp);
            string x = cross.X.ToString();
            string y = cross.Y.ToString();
            string z = cross.Z.ToString();

            cross.Normalize();
            if (cross.X.ToString().Contains("NaN")) cross.X = 0.0f;
            if (cross.Y.ToString().Contains("NaN")) cross.Y = 0.0f;
            if (cross.Z.ToString().Contains("NaN")) cross.Z = 0.0f;

            Quaternion targetQ = Quaternion.CreateFromAxisAngle(cross, theta);
            Slerp(targetQ, factor);
        }

        /// <summary>
        /// Force Object to Orient to UP Vector (Y)
        /// </summary>
        /// <param name="factor">Percentage of Rotation</param>
        public void ConstrainYAxis(float factor)
        {
            Quaternion Q = new Quaternion(0, RotationY, 0, Rotation.W);
            Slerp(Q, factor);
        }

        public void Draw()
        {
            //Convert objRotation to Vector3

            // Copy any parent transforms.
            Matrix[] transforms = new Matrix[object3D.Bones.Count];
            object3D.CopyAbsoluteBoneTransformsTo(transforms);

            // Draw the model. A model can have multiple meshes, so loop.
            foreach (ModelMesh mesh in object3D.Meshes)
            {
                // This is where the mesh orientation is set, as well as our camera and projection.
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateRotationY(objRotation.W) * Matrix.CreateTranslation(objPosition);
                    //effect.View = Matrix.CreateLookAt(cameraPosition, Vector3.Zero, Vector3.Forward);
                    effect.View = view;
                    effect.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 10000.0f);
                }
                // Draw the mesh, using the effects set above.
                mesh.Draw();
            }
        }


        #endregion

        #region Private Methods

        /// <summary>
        /// Rotate Object around specific Axis
        /// </summary>
        /// <param name="axis"></param>
        /// <param name="angle"></param>
        private void Rotate(Vector3 axis, float angle)
        {
            axis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(objRotation));
            objRotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * objRotation);
        }

        /// <summary>
        /// Translate Object along specific Axis
        /// </summary>
        /// <param name="distance"></param>
        private void Translate(Vector3 distance)
        { objPosition += Vector3.Transform(distance, Matrix.CreateFromQuaternion(objRotation)); }

        #endregion
    }
}